package com.lpb.spring.lpbspring.context;

import com.google.common.collect.Sets;
import com.lpb.spring.lpbspring.context.annotation.*;
import com.lpb.spring.lpbspring.ioc.BeanDefinitionRegistry;
import com.lpb.spring.lpbspring.ioc.BeanReference;
import com.lpb.spring.lpbspring.ioc.GenericBeanDefinition;
import com.lpb.spring.lpbspring.ioc.PropertyValue;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * @ClassName ClassPathBeanDefinitionScanner
 * @Description 解析bean对象的一种方式
 * @date 2022/12/19 02:03
 * @Version 1.0
 * @authoe linpengbo
 */
public class ClassPathBeanDefinitionScanner {

    private BeanDefinitionRegistry registry;

    private int classPathAbsLength = new File(ClassPathBeanDefinitionScanner.class.getResource("/").getPath()).getAbsolutePath().length();

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super();
        this.registry = registry;
    }


    /**
     * 扫描包下的类 解析注册bean对象
     *
     * @param basePackages
     * @throws Exception
     */
    public void scan(String... basePackages) throws Exception {
        if (basePackages != null && basePackages.length > 0) {
            for (String p : basePackages) {
                /**步骤
                 * 1、递归扫描包下的.class文件
                 * 2、组合包路径+class文件名 得到全限定类名
                 * 3、加载类 得到class对象
                 * 4、解析class上的注解 获取bean定义信息 注册bean的定义信息
                 */
                //1、递归扫描
                Set<File> calssFile = this.doScan(p);
                //2、根据扫描到的文件 得到class文件路径 并得到class对象 解析注解 注册bean定义
                this.readAndRegisterBeanDefinition(calssFile);
            }
        }

    }

    private void readAndRegisterBeanDefinition(Set<File> calssFiles) throws Exception {
        for (File classFile : calssFiles) {
              //获取类的全路径名
            String className = getClassNameFromFile(classFile);
            //加载类
            Class<?> clazz = this.getClass().getClassLoader().loadClass(className);
            //获取component注解 被修饰的都是让框架管理的类
            Component component = clazz.getAnnotation(Component.class);
            if(component!=null){
                //获取注解上配置的bean名称
                String beanName = component.name();
                if(StringUtils.isBlank(beanName)){
                    //若没配置 则取类名称
                    beanName=this.generateBeanName(clazz);
                }
                GenericBeanDefinition beanDefinition=new GenericBeanDefinition();
                beanDefinition.setBeanClass(clazz);
                //处理作用域
                Scope scope = clazz.getAnnotation(Scope.class);
                if(scope!=null){
                    beanDefinition.setScope(scope.value());
                }
                //处理primary
                Primary primary = clazz.getAnnotation(Primary.class);
                if(primary!=null){
                    beanDefinition.setPrimary(true);
                }
                //处理构造方法，在构造方法上找@Autowired注解，如有，将这个构造方法set到bd;
                this.handleConstructor(clazz,beanDefinition);
                //处理方法上的注解(找到类的初始化方法、类的销毁方法、注入bean用的工厂方法)
                this.handleMethod(clazz,beanDefinition,beanName);
                //处理属性依赖
                this.handlePropertyDI(clazz,beanDefinition);
                this.registry.registerBeanDefinition(beanName,beanDefinition);
            }
        }
    }

    /**解析bean的属性注入(解析bean里注入的 其他的bean)
     * @param clazz
     * @param bd
     */
    private void handlePropertyDI(Class<?> clazz, GenericBeanDefinition bd) {
        List<PropertyValue> propertyValues = new ArrayList<>();
        bd.setPropertyValues(propertyValues);
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                BeanReference beanReference = null;
                Qualifier qualifier = field.getAnnotation(Qualifier.class);
                if (qualifier != null) {
                    beanReference = new BeanReference(qualifier.value());
                } else {
                    beanReference = new BeanReference(field.getType());
                }
                PropertyValue propertyValue=new PropertyValue();
                propertyValue.setName(field.getName());
                propertyValue.setValue(beanReference);
                propertyValues.add(propertyValue);
            }
        }
    }

    private void handleMethod(Class<?> clazz, GenericBeanDefinition beanDefinition, String beanName) throws Exception {
        Method[] methods = clazz.getMethods();
        for(Method method:methods){
            if(method.isAnnotationPresent(PostConstruct.class)){
                beanDefinition.setInitMethodName(method.getName());
            }else if(method.isAnnotationPresent(PreDestroy.class)){
                //销毁方法
            }else if(method.isAnnotationPresent(Bean.class)){//生成bean的工厂方法
                this.handleFactoryMethod(method,clazz,beanName);
            }
        }
    }

    /** 静态工厂方法：对应的beanDefinition里的class是工厂类的class factoryMethodName是生成bean的工厂方法
     * 普通工厂方法： 对应的beanDefinition里的factorybeanName是工厂类对应的bean名称 factoryMethodName是生成bean的工厂方法
     * @param method
     * @param clazz
     * @param beanName
     */
    private void handleFactoryMethod(Method method, Class<?> clazz, String beanName) throws Exception {
        GenericBeanDefinition bd = new GenericBeanDefinition();
        //静态工厂方法
        if(Modifier.isStatic(method.getModifiers())){
            bd.setBeanClass(clazz);
        }else{
            //普通工厂bean的bean名称
            bd.setFactoryBeanName(beanName);
        }
        //工厂方法
        bd.setFactoryMethod(method);
        bd.setFactoryMethodName(method.getName());
        //处理scope
        Scope scope = method.getAnnotation(Scope.class);
        if(scope!=null){
            bd.setScope(scope.value());
        }
        //处理primary
        if(method.isAnnotationPresent(Primary.class)){
            bd.setPrimary(true);
        }
        //处理bean注解
        Bean bean = method.getAnnotation(Bean.class);
        //最终要构建的bean名称
        String xbeanName = bean.name();
        if(StringUtils.isBlank(xbeanName)){
            xbeanName=method.getName();
        }
        if(StringUtils.isNotBlank(bean.initMethod())){
            bd.setInitMethodName(bean.initMethod());
        }
        if(StringUtils.isNotBlank(bean.destroyMethod())){

        }
        //参数依赖处理
        bd.setConstructorArgumentValues(this.handleMethodParamters(method.getParameters()));

        //注册bean定义
        this.registry.registerBeanDefinition(xbeanName,bd);

    }


    /**处理构造函数 和 构造函数的入参
     * @param clazz
     * @param beanDefinition
     */
    private void handleConstructor(Class<?> clazz, GenericBeanDefinition beanDefinition) {
        //获得所有构造方法 在构造方法上找Autowired注解，如有 将这个构造方法set到beanDefinition
        Constructor<?>[] constructors = clazz.getConstructors();
        if(constructors!=null&&constructors.length>0){
            for(Constructor<?> cs:constructors){
                if(cs.getAnnotation(Autowired.class)!=null){
                    beanDefinition.setConstructor(cs);
                    //构造参数依赖处理
                    beanDefinition.setConstructorArgumentValues(this.handleMethodParamters(cs.getParameters()));
                }
            }
        }
    }

    //构造参数依赖处理方法
    private List<Object> handleMethodParamters(Parameter[] ps) {
        //遍历获取参数上的注解，及创建构造参数依赖
        List<Object> argValues = new ArrayList<>();
        for (Parameter parameter : ps) {
            //找@Value注解
            Value v = parameter.getAnnotation(Value.class);
            if (v != null) {
                argValues.add(v.value());
                continue;
            }
            //找@Qualifier
            Qualifier q = parameter.getAnnotation(Qualifier.class);
            if (q != null) {
                argValues.add(new BeanReference(q.value()));
            } else {
                argValues.add(new BeanReference(parameter.getType()));
            }
        }
        return argValues;
    }

    private String generateBeanName(Class<?> clazz) {
        //应用名称生成规则生成beanName:  类名首字母小写
        String className = clazz.getName();
        return className.substring(0, 1).toLowerCase() + className.substring(1);
    }

    private String getClassNameFromFile(File file) {
        String absPath = file.getAbsolutePath();
        String name = absPath.substring(classPathAbsLength + 1, absPath.indexOf('.'));
        return StringUtils.replace(name, File.separator, ".");
    }



    private Set<File> doScan(String p) throws Exception {
        //将包名转为路径名
        String basePackagePath = "/" + StringUtils.replace(p, ".", "/");
        //得到包对应的目录
        File rootDir = new File(this.getClass().getResource(basePackagePath).getPath());
        Set<File> scandClssFiles = Sets.newHashSet();
        this.dpRetrieveClassFiles(rootDir, scandClssFiles);
        return scandClssFiles;
    }

    /**
     * 递归指定目录下的所有类，所有.class结尾
     *
     * @param rootDir
     * @param scandClssFiles
     */
    private void dpRetrieveClassFiles(File rootDir, Set<File> scandClssFiles) {
        for (File file : rootDir.listFiles()) {
            if (file.isDirectory() && file.canRead()) {
                dpRetrieveClassFiles(file, scandClssFiles);
            }
            if (file.getName().endsWith(".class")) {
                scandClssFiles.add(file);
            }
        }

    }


}
